package com.atguigu.gulimall.seckill.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.mq.OrderTo;
import com.atguigu.common.to.mq.SeckillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberEntity;
import com.atguigu.gulimall.seckill.feign.CouponFeignService;
import com.atguigu.gulimall.seckill.feign.ProductFeignService;
import com.atguigu.gulimall.seckill.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.seckill.service.SeckillService;
import com.atguigu.gulimall.seckill.to.SecKillSkuRedisTo;
import com.atguigu.gulimall.seckill.vo.SeckillSessionsWithSkus;
import com.atguigu.gulimall.seckill.vo.SeckillSkuVo;
import com.atguigu.gulimall.seckill.vo.SkuInfoVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.parsers.ITableNameHandler;
import com.sun.javafx.sg.prism.NGPointLight;
import org.apache.commons.io.TaggedIOException;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.asn1.cmp.RevAnnContent;
import org.checkerframework.checker.units.qual.A;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import sun.util.resources.cldr.hy.CalendarData_hy_AM;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private CouponFeignService couponFeignService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    private   final String  SESSIONS_CACHE_PREFIX="seckill:sessions:";//  活动要用的前缀
    private   final String  SKUKILL_CACHE_PREFIX="seckill:skus";//  活动要用的前缀
    private final   String   SKU_STOCK_SEMAPHORE="seckill:stock:";
    @Override
    public void uploadSeckillSkuLatest3Days() {
        //1.去数据库扫描 ,最近3天参加秒杀的商品
        R latest3DaySession = couponFeignService.getLatest3DaySession();
        if(latest3DaySession.getCode()==0){
            List<SeckillSessionsWithSkus> data = latest3DaySession.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {
            });
            saveSessionInfos(data);
            saveSessionSkuInfos(data);
        }
    }
    @Override
    public void uploadSeckillSkuLatest3Days2() {
        R latest3DaySession = couponFeignService.getLatest3DaySession();
        if(latest3DaySession.getCode()==0){
            List<SeckillSessionsWithSkus> data = latest3DaySession.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {});
            saveSessionInfos2(data);
            saveSessionSkuInfos2(data);
        }
    }
    @Override
    public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {  //  获取当前场次的秒杀商品
        //1 确定当前时间属于哪个秒杀场次
        long time = new Date().getTime();  // 获取现在的时间
        Set<String> keys = stringRedisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");  //seckill:skus  //  获取所有的key
        for (String key : keys) {// 遍历所有的key
            String replace = key.replace(SESSIONS_CACHE_PREFIX, "");  //seckill:sessions:
            String[] s = replace.split("_");  //然后分隔 ,但是 这里本来得到的是什么?
            Long start= Long.parseLong(s[0]);//   s是一个数组,把第0项 为 开始
            Long end =Long.parseLong(s[1]);//  把 第1 项 为 结束
            if(time>=start  &&   time<=end){//   判断 :  如果当前时间  大于等于开始时间, 而且 小于等于 结束时间  就是说,如果当前时间在  开始时间 和 结束时间 的状态. 也就是说,当前时间如果属于正在进行中的话.
                List<String> range = stringRedisTemplate.opsForList().range(key, -100, 100);// 那么就: 取出数据. 取出所有的数据.
                BoundHashOperations<String, String, String> hashOps= stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);//seckill:skus    绑定redis操作
                List<String> list = hashOps.multiGet(range);     //range 的 结果是什么?  这里list的值是什么?
                if(list!=null ){
                    List<SecKillSkuRedisTo> collect = list.stream().map(item -> {     //   item  是String类型
                        SecKillSkuRedisTo redisTo = JSON.parseObject(item, SecKillSkuRedisTo.class);
                        return redisTo;
                    }).collect(Collectors.toList());
return  collect;
                }
                break;
            }
        }
        //2. 获取这个秒杀场次需要的所有商品信息
        return null;
    }
    @Override
    public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        BoundHashOperations<String, String , String > hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);//seckill:skus  绑定hash操作
        Set<String> keys = hashOps.keys();   //获取所有的key ,问题:什么时候存进去的?
        if( keys !=null     &&     keys.size() > 0){
            String   regx="\\d_" + skuId;  // \\d_ 代表什么意思?　
            for (String key : keys) {
                if(Pattern.matches(regx,key)){
                    String json= hashOps.get(key);
                    SecKillSkuRedisTo skuRedisTo= JSON.parseObject(json, SecKillSkuRedisTo.class);
                    long current = new Date().getTime();
                    if(current>=skuRedisTo.getStartTime()   &&     current<=skuRedisTo.getEndTime()){
                    }else{
            skuRedisTo.setRandomCode(null);
                    }
                    return  skuRedisTo;
                }
            }
        }
        return null;
    }
    @Override
    public String kill(String killId, String key, Integer num) {
        MemberEntity member = LoginUserInterceptor.loginUser.get();  //  获取已经登录的用户
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);  //绑定hash操作 seckill:skus  问题：什么时候存进去的？
        String json = hashOps.get(killId);// 通过key获取值
        if(StringUtils.isEmpty(json)){
        }else{
            SecKillSkuRedisTo redisTo = JSON.parseObject(json, SecKillSkuRedisTo.class);  //    转成对象
            Long startTime = redisTo.getStartTime();  //   获取开始时间
            Long endTime = redisTo.getEndTime(); //  获取结束时间
            long time = new Date().getTime();  //获取当前时间
            if(time  >=  startTime  &&    time<=  endTime){//校验时间的合法性
                //校验随机码
                String randomCode = redisTo.getRandomCode();  //  随机码的作用是什么?
                String skuId = redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();  // 这个是什么意思？　　　场次id _ skuId
                if(randomCode.equals(key)  &&     killId.equals(skuId)){
                    //  验证购物数量是否合理
                    if(num <=redisTo.getSeckillLimit()){
                        // 验证这个人是否已经购买过了   幂等性,如果只要秒杀成功,就去占位
                        String  redisKey= member.getId()+"_"+skuId;      //  用户id +  商品id      在这里 ,我们使用  用户id+商品id去占位置
                        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), (endTime - time), TimeUnit.MICROSECONDS);
                        if(b){
                            //说明占位成功,说明这个人从来没买过 ,  然后去下单
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode); //  seckill:stock:    去获取库存,
//                                boolean b1 = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                boolean b1 = semaphore.tryAcquire(num); //  如果能获取库存,就可以下单
                                if(b1){ //  说明成功获取到库存了
                                    String timeId = IdWorker.getTimeId();//   示例值:202408290037326631828834311700779009   解析: 2024 08 29 00 37 326631828834311700779009      //  生成订单号
                                    SeckillOrderTo orderTo = new SeckillOrderTo(); // 创建秒杀场景下的订单对象
                                    orderTo.setOrderSn(timeId);  //设置订单编号
                                    orderTo.setMemberId(member.getId()); //设置会员id
                                    orderTo.setNum(num);//  设置 购买数量.
                                    orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());  //  设置 场次 id
                                    orderTo.setSkuId(redisTo.getSkuId());  //设置 sku_id
                                    orderTo.setSeckillPrice(redisTo.getSeckillPrice());  //  设置 sku的秒杀价格
                                    rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo); //发送消息队列  ,然后订单微服务会监听这个队列
                                    //   这个消息,最终会发送到   order.seckill.order.queue  队列
                                    return timeId;        //  返回订单编号
                                }
                                     return  null;
                        }else {
                            // 说明已经买过了
                            return  null;
                        }
                    }
                }else {
                    return null;
                }
            }else {
                return null;
            }
        }
        return null;
    }
    public String kill2(String killId, String key, Integer num) {   //  killId从前端传过来, 是场次id_skuId ,key从前端传过来,表示随机码,num从前端传过来,表示要购买几个
        MemberEntity member = LoginUserInterceptor.loginUser.get();  //  获取已经登录的用户
        SeckillOrderTo seckillOrderTo= new SeckillOrderTo();
        seckillOrderTo.setNum(num);
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);  //绑定hash操作 seckill:skus  问题：什么时候存进去的？
             String json = hashOps.get(killId);// 通过key获取值
        SecKillSkuRedisTo redisTo = JSON.parseObject(json, SecKillSkuRedisTo.class);  //    转成对象
            String randomCode = redisTo.getRandomCode();  //  随机码的作用是什么? 每个参加秒杀的商品,都有一个专属于自己的随机码
            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
    Boolean b=semaphore.tryAcquire(num);
        Long startTime = redisTo.getStartTime();  //   获取开始时间
        Long endTime = redisTo.getEndTime(); //  获取结束时间    //  这里获取到是活动的结束时间
        long time = new Date().getTime();  //获取当前时间  //  这里获取到是此时此刻的时间

            String skuId = redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();  // 这个是什么意思？　　　场次id _ skuId

   //     randomCode.equals(key); // 校验随机码
     //      killId.equals(skuId); //   校验: 你要买的商品 和  缓存中准备的商品 是一样

        if(  randomCode.equals(key)    &&   killId.equals(skuId)){
            if(time >=   startTime  &&   time <=  endTime ){ // 这个是校验时间,如果当前时间在活动的范围时间内,就可以买,不在的话说明秒杀都结束了,就不能买
                if(num<=  redisTo.getSeckillLimit()){ //  看下单个用户想要买的件数, 是否小于等于限购数量,   如果是小于等于的,才可以买,如果不行,
                    //  就算他可以买,但是,还是要验证一下,验证他之前有没有买过,之前买过的也不能买

                    String redisKey  = member.getId()+"_"+skuId; //  标识位

                    //  把哪个用户要买,要买几个的信息存入redis中,并且设置过期时间.这是一个临时数据,当活动结束,就删除,会返回一个布尔值,如果成功,说明可以购买
                    //  但是这里会产生一个bug,就是,如果限量3个,用户第一次买了2个,在秒杀时间内,用户还想买2个,这样就会导致用户不能买剩下的两个.我们先不要管这个问题,假定现在用户只能买一次
                    Boolean b1 = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), (endTime - time), TimeUnit.MICROSECONDS);
                    if(b1){ //   说明占位成功
                        if(b){  //  用户要去redis拿库存,  能拿到说明可以下单,拿不到就不可以下单
                            String timeId = IdWorker.getTimeId();
                            seckillOrderTo.setOrderSn(timeId);

                            seckillOrderTo.setMemberId(member.getId());
                            seckillOrderTo.setNum(num);
                            seckillOrderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
                            seckillOrderTo.setSkuId(redisTo.getSkuId());
                            seckillOrderTo.setSeckillPrice(redisTo.getSeckillPrice());
                            rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",seckillOrderTo);
                            return timeId;//     返回订单编号
                        }else{  //  说明拿不到,直接返回空值
                            return  null;
                        }

                    }else {
                        return  null;
                    }



                }else{
                    return   null;
                }


            }else{
                return null;    //不能买,就直接返回false
            }

        }else {
            return  null;
        }
















    }
    private  void saveSessionInfos( List<SeckillSessionsWithSkus> data){   //  保存 秒杀活动的场次信息
        //  先把data遍历,那么,data是什么东西?   data是一个数组,是  所有 秒杀的场次
        data.stream().forEach(one -> { //   one在这里 代表的是 某一个秒杀的场次
            Long startTime = one.getStartTime().getTime();//  这个场次的开始时间
            Long endTime = one.getEndTime().getTime();//这个场次的结束时间
            String key=  SESSIONS_CACHE_PREFIX+startTime+"_"+endTime;//seckill:sessions:     示例: seckill:sessions:1724863582786_1724864045909
            Boolean hasKey = stringRedisTemplate.hasKey(key);  // 去redis中,查询有没有这个key,  如果有的话,是什么时候存进去的呢?
            if(!hasKey){
                List<String> collect = one.getRelationSkus().stream().map(item -> { // one.getRelationSkus()代表的是,某个秒杀场次中,包括的所有sku.
                    return item.getPromotionSessionId()+"_"+item.getSkuId().toString();     //  把 场次id 和 sku_id 拼接一下.
                }).collect(Collectors.toList());
            stringRedisTemplate.opsForList().leftPushAll(key,collect);   //  保存数据.  List<String(场次id_skuid)>
            }
        });
    }
    private  void saveSessionInfos2( List<SeckillSessionsWithSkus> data){   //  保存 秒杀活动的场次信息
        data.stream().forEach(one -> {
            long startTime = one.getStartTime().getTime();
            long endTime = one.getStartTime().getTime();
            String key=SESSIONS_CACHE_PREFIX+startTime+"_"+endTime;      //"seckill:sessions:"
            Boolean hasKey = stringRedisTemplate.hasKey(key);
            if(!hasKey){
                List<String> collect = one.getRelationSkus().stream().map(item -> {
                    return item.getPromotionSessionId() + "_" + item.getSkuId().toString();
                }).collect(Collectors.toList());
                stringRedisTemplate.opsForList().leftPushAll(key,collect);
            }
        });
    }
    private void   saveSessionSkuInfos( List<SeckillSessionsWithSkus> data){ //  保存 单个场次里面包含的所有sku
        BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);  // seckill:skus  获取hash操作
        //  data代表的是 所有  秒伤的场次
        data.stream().forEach( one -> { // one代表的是单个秒杀场次
            String token= UUID.randomUUID().toString().replace("-", "");  //  生成一个随机数
            one.getRelationSkus().stream().forEach( item -> { //   one.getRelationSkus() 代表的是  单个场次里所有的sku,现在要遍历这些sku
                Boolean b1 = hashOps.hasKey(item.getPromotionSessionId().toString()+"_"+item.getSkuId().toString()); //  有这个key吗?以前存过这个key吗?
                if(!b1){//  如果没有的话.
                    SecKillSkuRedisTo redisTo= new SecKillSkuRedisTo();  // 创建对象,  创建 sku对象(要保存在redis中的sku对象)
                    R skuInfo = productFeignService.getSkuInfo(item.getSkuId());  //  根据sku_id,获取这个sku的详细信息
                    if(skuInfo.getCode()==0){
                        SkuInfoVo info= skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                        redisTo.setSkuInfo(info);//  赋值
                    }
                    BeanUtils.copyProperties(item,redisTo); //  赋值
                    redisTo.setStartTime(one.getStartTime().getTime()); //赋值
                    redisTo.setEndTime(one.getEndTime().getTime());// 赋值
                    // 随机码
                    redisTo.setRandomCode(token); // 赋值
                    hashOps.put(item.getPromotionSessionId().toString()+"_"+item.getSkuId().toString(), JSON.toJSONString(redisTo));
                    //如果当前这个场次的商品的库存信息已经上架
                    // 就不需要上架
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);   //          使用库存作为分布式的信号量 : 限流 参数示例值: seckill:stock:794e487cfc614f93ab595b1df7a78642
                    semaphore.trySetPermits(item.getSeckillCount());    //然后向  redis中存入  库存信息
//                    semaphore.trySetPermits(857);
                }
            });
        });
    }
    private void   saveSessionSkuInfos2( List<SeckillSessionsWithSkus> data){ //  保存 单个场次里面包含的所有sku
        BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);//seckill:skus
        String token= UUID.randomUUID().toString().replace("-", "");  //  生成一个随机数
        data.stream().forEach( one -> {
            one.getRelationSkus().stream().forEach(  item -> {
                Boolean b = hashOps.hasKey(item.getPromotionSessionId().toString() + "_" + item.getSkuId().toString());
                if(!b){
                    SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
                    R skuInfo = productFeignService.getSkuInfo(item.getSkuId());
                    if(skuInfo.getCode()==0){
                        SkuInfoVo skuInfo1 = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                        redisTo.setSkuInfo(skuInfo1);
                    }
                    BeanUtils.copyProperties(item,redisTo);
                    redisTo.setStartTime(one.getStartTime().getTime());
                    redisTo.setEndTime(one.getEndTime().getTime());
                    redisTo.setRandomCode(token);
                    /*开始正式上架!!!!*/     hashOps.put(item.getPromotionSessionId().toString() + "_"  +item.getSkuId().toString() ,  JSON.toJSONString(redisTo));
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);//seckill:stock:
//                    semaphore.trySetPermits(item.getSeckillCount());   //   存储redis
                    semaphore.trySetPermits(857);
                }
            });
        });
    }
}
